#include "ThreadManejador.h"
#include "Respuesta.h"
#include "RespuestaBitField.h"
#include "RespuestaTengo.h"
#include "InformacionPieza.h"
#include "InterfazPeer.h"

ThreadManejador::ThreadManejador(InterfazPeer* interfazPeer, Condition *conditionCLlegada, queue<Respuesta*> *colaLlegada, Mutex *mutexPiezas, list<InformacionPieza*> *piezas, Mutex *mutexPiezasABajarOBajadas, list<unsigned long int> *piezasPorBajar, list<unsigned long int> *piezasBajadas, Condition* conditionChoke, Condition* conditionTerminoBajado)
{
	this->interfazPeer = interfazPeer;

	this->conditionCLlegada = conditionCLlegada;
	this->colaLlegada = colaLlegada;

	this->mutexPiezas = mutexPiezas;
	this->piezas = piezas;

	this->mutexPiezasABajarOBajadas = mutexPiezasABajarOBajadas;
	this->piezasPorBajar = piezasPorBajar;
	this->piezasBajadas = piezasBajadas;

	this->conditionChoke = conditionChoke;

	this->conditionTerminoBajado = conditionTerminoBajado;
}

ThreadManejador::~ThreadManejador()
{
}

void* ThreadManejador::run()
{
	Respuesta *respuesta;

	while(!this->getStop()){

		this->conditionCLlegada->mutexLock();

		if(this->colaLlegada->empty()){
			this->conditionCLlegada->wait();
		}

		if(this->getStop()) {
			this->conditionCLlegada->mutexUnlock();
			//TODO ver de matar todos los threads. La funcion que lo hace debe hacerlo una sola vez. Para ello utilizar una bandera
			return NULL;
		}

		respuesta = this->colaLlegada->front();
		this->colaLlegada->pop();

		this->conditionCLlegada->mutexUnlock();

		if(respuesta != NULL){
			/* TODO agregar chequeo bitfield
			 * A bitfield of the wrong length is considered an error. Clients should drop the connection if they receive bitfields that are not of the correct size, or if the bitfield has any of the spare bits set. */

			if(respuesta->getTipo() == "choke"){
				this->interfazPeer->setPeerChoking(true);
			} else if(respuesta->getTipo() == "unchoke"){
				this->interfazPeer->setPeerChoking(false);
				this->conditionChoke->signal();
			} else if(respuesta->getTipo() == "interesado"){
				this->interfazPeer->setPeerInteresado(true);
			} else if(respuesta->getTipo() == "no interesado"){
				this->interfazPeer->setPeerInteresado(false);
			} else if (respuesta->getTipo() == "bitfield"){
				RespuestaBitField* respuestaBitfield = dynamic_cast<RespuestaBitField*>(respuesta);

				if(respuestaBitfield != NULL) {
					this->agregarCandidatoPiezas(respuestaBitfield->getPiezas(), respuestaBitfield->getPeerId());

					this->mutexPiezasABajarOBajadas->lock();
					list<unsigned long int> listaAux = *this->piezasBajadas;
					this->mutexPiezasABajarOBajadas->unlock();

					this->interfazPeer->bitfield(listaAux);

					delete respuesta;
				}
			} else if (respuesta->getTipo() == "tengo") {
				RespuestaTengo* respuestaTengo = dynamic_cast<RespuestaTengo*>(respuesta);

				if (respuestaTengo != NULL) {
					list<unsigned long int> listAux;
					listAux.push_back(respuestaTengo->getIdPieza());
					this->agregarCandidatoPiezas(listAux, respuestaTengo->getPeerId());

					delete respuesta;
				}
			} else if (respuesta->getTipo() == "pedido") {
				cout << "Falta implementar nuestra respuesta a las demandas de partes de los peers" << endl; //TODO hay que implementar la respuesta a un pedido de parte
			} else if (respuesta->getTipo() == "cancelar" ){
				cout << "Falta implementar nuestra respuesta a las demandas de partes de los peers" << endl; //TODO hay que implementar la respuesta a un cancelar de la parte
			}
		}
	}

	return NULL;
}

void ThreadManejador::agregarCandidatoPiezas(const list<unsigned long int> &idPiezas, const string &idPeer) { //TODO REVISAR MUTEX, RACE CONDITION. Creo que anda bien, probar.

	bool encontrado;

	list<unsigned long int>::const_iterator itIdPiezas = idPiezas.begin();

	while ( itIdPiezas != idPiezas.end()) {

		unsigned long int idActual = *itIdPiezas;
		itIdPiezas++;

		this->mutexPiezasABajarOBajadas->lock();

		list<unsigned long int>::iterator itPiezasBajadas = this->piezasBajadas->begin();
		encontrado = false;

		/* Primero busco si la pieza no se encuentra bajada */
		while ((!encontrado) && (itPiezasBajadas != this->piezasBajadas->end())) {
			unsigned long int piezaActual = *itPiezasBajadas;

			if (idActual == piezaActual) {
				encontrado = true;
				this->mutexPiezasABajarOBajadas->unlock();
			}
			itPiezasBajadas++;
		}

		if (!encontrado) {
			list<unsigned long int>::iterator itPiezasPorBajar = this->piezasPorBajar->begin();

			/* Busco si la pieza se encontraba en la lista de piezas por bajar */
			while ((!encontrado) && (itPiezasPorBajar != this->piezasPorBajar->end())) {
				unsigned long int piezaActual = *itPiezasPorBajar;

				if (idActual == piezaActual) {
					encontrado = true;
					this->piezasPorBajar->erase(itPiezasPorBajar);
				}

				itPiezasPorBajar++;
			}

			this->mutexPiezasABajarOBajadas->unlock();

			this->mutexPiezas->lock();
			/* Si encontro la pieza debo crear una nueva clase InformacionPieza y enlistarla en la lista de piezas */
			if (encontrado) {
				InformacionPieza* pieza = new InformacionPieza(idActual);
				pieza->agregarCandidato(idPeer);
				this->piezas->push_back(pieza);
				this->mutexPiezas->unlock();
			} else {
				list<InformacionPieza*>::iterator itPiezas = this->piezas->begin();
				encontrado = false;

				while ((!encontrado) && (itPiezas != this->piezas->end())) {
					InformacionPieza* informacionPiezaActual = *itPiezas;

					if(idActual == informacionPiezaActual->getNroPieza()) {
						informacionPiezaActual->agregarCandidato(idPeer);
						encontrado = true;
					}
				}

				this->mutexPiezas->unlock();
			}
		}
	}
}

void ThreadManejador::stop() //TODO implementar bien stop, ver el stop de los otros threads
{
	this->Thread::stop();
}
